﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Timers;
using System.Diagnostics;
using Phys.Model.Affectors;
using TWiStEr.Math;

namespace Phys.Model {
	public class WorldObject : CompositeObject {
		public WorldObject() {
			TimeScale = 1;
			timer.Elapsed += timer_Elapsed;
		}
		Timer timer = new Timer();
		public void Start() {
			timer.Start();
			OnStarted();
		}
		public bool IsStarted { get { return timer.Enabled; } }
		public void Stop() {
			timer.Stop();
			last = null;
			OnStopped();
		}

		private DateTime? last;
		protected bool running = false;
		private void timer_Elapsed(object sender, ElapsedEventArgs e) {
			if (running) return;
			if (!last.HasValue) {
				last = DateTime.Now; System.Threading.Thread.CurrentThread.Name = "Physics"; return;
			}
			running = true;
			double tickLength = (DateTime.Now - last.Value).TotalSeconds;
			SetCollided(this, false);
			OnAffect(this, tickLength * TimeScale);
			Debug.WriteLine(tickLength * TimeScale + " ");
			last = DateTime.Now;
			OnUpdated();
			running = false;
		}

		private void SetCollided(IObject obj, bool isCollided) {
			if (obj is CompositeObject) foreach (IObject o in ((CompositeObject)obj).Objects) SetCollided(o, isCollided);
			CollisionAffector coll = obj.Affectors.OfType<CollisionAffector>().SingleOrDefault();
			if (coll != null) coll.IsCollided = isCollided;
		}

		private void OnAffect(IObject obj, double seconds) {
			if (obj is CompositeObject) foreach (IObject o in ((CompositeObject)obj).Objects) OnAffect(o, seconds);

			// Accumulate the forces with affectors
			obj.Forces = Vector.Zero;
			foreach (IAffector aff in obj.Affectors) {
				aff.Affect(obj, seconds);
			}
			// Calculate acceleration from forces
			Vector acceleration = obj.Forces / obj.Mass;
			// Calculate displacement in a given time frame (Fizika 9 @ 27. oldal: 27.2 ábra)
			Vector speed = obj.Velocity * seconds + acceleration * Math.Pow(seconds, 2) / 2;
			if (obj.Mass != 0.0) // if Mass is 0, then velocity is a mess: NaN
				obj.Position += speed;

			obj.Velocity += acceleration * seconds;
		}

		/// <summary>
		/// Gets or sets how long should it take between two updates.
		/// This property is in sync with FPS.
		/// </summary>
		public double TickLength {
			get { return timer.Interval; }
			set { timer.Interval = value; }
		}

		/// <summary>
		/// Gets or sets frames per second.
		/// Manages how many updates should occur in a second.
		/// This property is in sync with TickLength.
		/// </summary>
		public double FPS {
			get { return 1000 / timer.Interval; }
			set { timer.Interval = 1000 / value; }
		}

		/// <summary>
		/// Gets or sets how much the time should be compressed.
		/// Eg.: setting to 0.1 would makes things 10 times slower.
		/// </summary>
		public double TimeScale { get; set; }

		#region Events
		public event WorldEvent Started;
		protected void OnStarted() {
			if (Started != null) Started(this, new WorldEventArgs() { Type = WorldEventType.Started });
		}
		public event WorldEvent Stopped;
		protected void OnStopped() {
			if (Stopped != null) Stopped(this, new WorldEventArgs() { Type = WorldEventType.Stopped });
		}
		public event WorldEvent Updated;
		protected void OnUpdated() {
			if (Updated != null) Updated(this, new WorldEventArgs() { Type = WorldEventType.Updated });
		}
		#endregion
	}
	public delegate void WorldEvent(object sender, WorldEventArgs e);
	public class WorldEventArgs : EventArgs {
		public WorldEventArgs() { }
		public WorldEventType Type;
	}
	public enum WorldEventType { Started, Stopped, Updated };
}
